package service.impl;

import com.google.common.collect.ImmutableList;
import common.consts.*;
import dao.*;
import domain.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import service.PayService;
import service.ServiceResponse;
import service.util.MessagePushUtil;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * Created by aceyin on 15/7/13.
 */
@Service
public class PayServiceImpl extends ServiceBase implements PayService {

    @Autowired
    private RecruitmentDao recruitmentDao;
    @Autowired
    private OfferDao offerDao;
    @Autowired
    private TransactionDao transactionDao;
    @Autowired
    private RewardDao rewardDao;
    @Autowired
    private BalanceDao balanceDao;
    @Autowired
    private PushMessageDao pushMessageDao;
    @Autowired
    private UserDataProfileDao userDataProfileDao;
    @Autowired
    private WithholdDao withholdDao;

    /**
     * 支付工资给用户。
     * <p>
     * 先判断当前user是否有应聘employer的recruitment所对应的工作。
     * 如果没有报错，否则检查recruitment是否结束。
     * <p>
     * 然后将recruitment对应的工资支付给user。
     * <p>
     * 接着查询 工作奖励 表，如果这个recruitment有特别奖励，同时将奖励的金额发放到user钱包。
     * <p>
     * 然后发送推送消息。
     *
     * @param recruitmentId
     * @param user
     * @param employer
     * @return
     */
    @Override
    @Transactional(readOnly = false)
    public ServiceResponse pay(long recruitmentId, long user, long employer) {
        // 检查offer
        Offer offer = offerDao.findOffer(recruitmentId, user);
        ServiceResponse response = checkOffer(employer, offer);
        if (response.isFailed()) {
            return response;
        }

        Recruitment recruitment = recruitmentDao.get(recruitmentId);
        response = checkRecruitment(recruitment);
        if (response.isFailed()) {
            return response;
        }

        List<Offer> offers = ImmutableList.<Offer>builder().add(offer).build();

        // 获取支付方式，金额
        String payType = recruitment.getPayType();
        if (Recruitments.PayTypes.isCash(payType)) {
            response = payCash(offers, recruitment);
        } else {
            response = payOnline(offers, recruitment);
        }

        return response;
    }

    @Override
    public ServiceResponse payMultiple(long recruitmentId, long employerId) {
        Recruitment recruitment = recruitmentDao.get(recruitmentId);
        ServiceResponse response = checkRecruitment(recruitment);
        if (response.isFailed()) {
            return ServiceResponse.fail(Consts.i18nKey.UNKNOWN_RECRUITMENT);
        }
        long owner = recruitment.getOwner();
        if (owner != employerId) {
            return ServiceResponse.fail(Consts.i18nKey.INVALID_REQUEST);
        }
        String payType = recruitment.getPayType();

        List<Offer> offers = offerDao.getAllOffersByStatus(recruitmentId, employerId, Offers.Status.EMPLOYED.name());
        if (Recruitments.PayTypes.isCash(payType)) {
            response = payCash(offers, recruitment);
        } else {
            response = payOnline(offers, recruitment);
        }
        return response;
    }

    /**
     * 拒付工资
     *
     * @param employer
     * @param user
     * @param recruitmentId
     * @param reason
     * @return
     */
    @Override
    @Transactional(readOnly = false)
    public ServiceResponse withhold(long employer, long user, long recruitmentId, String reason) {
        // 检查offer
        Offer offer = offerDao.findOffer(recruitmentId, user);
        ServiceResponse response = checkOffer(employer, offer);
        if (response.isFailed()) {
            return response;
        }

        Recruitment recruitment = recruitmentDao.get(recruitmentId);
        response = checkRecruitment(recruitment);
        if (response.isFailed()) {
            return response;
        }

        Withhold withhold = new Withhold();
        withhold.setAmount(recruitment.getSalary());
        withhold.setPayee(user);
        withhold.setPayer(employer);
        withhold.setReason(reason == null ? "" : reason);
        withhold.setRecruitmentId(recruitmentId);
        withhold.setWithholdTime(new Date());
        withholdDao.insert(withhold);

        //update offer status
        ArrayList<Long> ids = new ArrayList<>();
        ids.add(offer.getId());
        offerDao.updatePayResult(ids, Offers.PayResult.WITHHOLD.name());

        //TODO: SalerName
        PushMessage message = MessagePushUtil.createWithholdMessage(user, "", reason);
        pushMessageDao.insert(message);

        return ServiceResponse.success();
    }

    @Override
    @Transactional(readOnly = false)
    public ServiceResponse withholdMultiple(long employerId, long recruitmentId, String reason) {
        Recruitment recruitment = recruitmentDao.get(recruitmentId);
        ServiceResponse response = checkRecruitment(recruitment);
        if (response.isFailed()) {
            return response;
        }
        List<Offer> offers = offerDao.getAllOffersByStatus(recruitmentId, employerId, Offers.Status.EMPLOYED.name());
        if (offers == null || offers.isEmpty()) {
            return ServiceResponse.fail(Consts.i18nKey.NO_OFFER_CAN_BE_WITHHOLD);
        }
        List<Long> ids = new ArrayList<>();
        List<Withhold> list = new ArrayList<>();
        for (Offer offer : offers) {
            Withhold withhold = new Withhold();
            withhold.setAmount(recruitment.getSalary());
            withhold.setPayee(offer.getApplier());
            withhold.setPayer(offer.getEmployer());
            withhold.setReason(reason == null ? "" : reason);
            withhold.setRecruitmentId(recruitmentId);
            withhold.setWithholdTime(new Date());
            list.add(withhold);

            ids.add(offer.getId());
        }

        withholdDao.batchInsert(list);
        offerDao.updatePayResult(ids, Offers.PayResult.WITHHOLD.name());

        return ServiceResponse.success();
    }

    private ServiceResponse checkOffer(long employer, Offer offer) {
        if (offer == null) {
            return ServiceResponse.fail(Consts.i18nKey.UNKNOWN_OFFER);
        }
        long offerEmployer = offer.getEmployer();
        if (offerEmployer != employer) {
            return ServiceResponse.fail(Consts.i18nKey.OFFER_EMPLOYER_IS_INVALID);
        }
        String status = offer.getStatus();
        if (!Offers.isEmployed(status)) {
            return ServiceResponse.fail(Consts.i18nKey.INVALID_OFFER_STATUS);
        }
        String payResult = offer.getPayResult();
        if (Offers.isPayed(payResult)) {
            return ServiceResponse.fail(Consts.i18nKey.OFFER_HAS_BEEN_PAYED);
        }
        return ServiceResponse.success();
    }

    private ServiceResponse payOnline(List<Offer> offers, Recruitment recruitment) {
        return ServiceResponse.fail(Consts.i18nKey.ONLINE_PAY_NOT_SUPPORTED_YET);
    }

    /**
     * 发送现金工资。实际上只是插入一些transaction记录，并不会更新用户的balance。
     *
     * @param offers
     * @param recruitment
     * @return
     */
    private ServiceResponse payCash(List<Offer> offers, Recruitment recruitment) {
        Long recruitmentId = recruitment.getId();

        // 交易流水
        List<Transaction> transactions = new ArrayList<>();
        // 务工者id
        List<Long> appliers = new ArrayList<>();
        // 推送消息
        List<PushMessage> messages = new ArrayList<>();
        // offerids
        List<Long> offerIds = new ArrayList<>();

        for (Offer offer : offers) {
            long applier = offer.getApplier();

            offerIds.add(offer.getId());

            // 工资流水
            Transaction transaction = createWagesTransaction(offer, recruitment);
            transactions.add(transaction);
            // 插入一条奖金流水
            Transaction bonusTransaction = createBonusTransaction(recruitmentId, applier);
            if (bonusTransaction != null) {
                transactions.add(bonusTransaction);
            }

            // 保存推送消息
            // TODO: senderName
            PushMessage message = MessagePushUtil.createPayMessage(applier, "");
            messages.add(message);
            // applier ids
            appliers.add(applier);
        }

        // 更新用户数据
        transactionDao.batchInsert(transactions);
        userDataProfileDao.increaseWorkDoneTimes(appliers);
        pushMessageDao.batchInsert(messages);
        offerDao.updatePayResult(offerIds, Offers.PayResult.PAYED.name());
        return ServiceResponse.success();
    }

    private Transaction createBonusTransaction(long recruitmentId, long applier) {
        Reward reward = rewardDao.findByRecruitmentAndStatus(recruitmentId, Commons.Status.NORMAL.name());
        if (reward == null) {
            return null;
        }
        int bonus = reward.getAmount();
        String rewardType = reward.getType();

        boolean giveBonus = reward != null && bonus > 0;

        if (giveBonus && Rewards.isMoney(rewardType)) {
            Transaction bonusTransaction = new Transaction();
            bonusTransaction.setAmount(bonus);
            bonusTransaction.setPayee(applier);
            bonusTransaction.setPayeeAccount("");
            bonusTransaction.setPayItem(Pays.Item.BONUS.name());
            bonusTransaction.setPayer(Consts.SYSTEM_ACCOUNT_ID);
            bonusTransaction.setPayerAccount("");
            bonusTransaction.setPayTime(new Date());
            bonusTransaction.setPayType(Recruitments.PayTypes.CASH.name());
            bonusTransaction.setPayResult(Pays.Result.SUCCESS.name());
            bonusTransaction.setTransid("");
            return bonusTransaction;
        }
        return null;
    }

    private Transaction createWagesTransaction(Offer offer, Recruitment recruitment) {
        long applier = offer.getApplier();
        Integer salary = recruitment.getSalary();
        // 保存交易流水
        Transaction transaction = new Transaction();
        transaction.setAmount(salary);
        transaction.setPayee(applier);
        transaction.setPayeeAccount("");
        transaction.setPayItem(Pays.Item.WAGES.name());
        transaction.setPayer(recruitment.getOwner());
        transaction.setPayerAccount("");
        transaction.setPayTime(new Date());
        transaction.setPayType(Recruitments.PayTypes.CASH.name());
        transaction.setPayResult(Pays.Result.SUCCESS.name());
        transaction.setTransid("");
        return transaction;
    }

    private ServiceResponse checkRecruitment(Recruitment recruitment) {
        if (recruitment == null) {
            return ServiceResponse.fail(Consts.i18nKey.UNKNOWN_RECRUITMENT);
        }
        Date now = new Date();
        Date workEndDate = recruitment.getWorkEndDate();
        if (workEndDate.before(now)) {
            return ServiceResponse.fail(Consts.i18nKey.WORK_NOT_END_YET);
        }
        String status = recruitment.getStatus();
        if (!Recruitments.Status.isNormal(status)) {
            return ServiceResponse.fail(Consts.i18nKey.RECRUITMENT_IS_ABNORMAL);
        }
        return ServiceResponse.success();
    }

}
